Blazor Workshop Part 1

This section will guide you through the fundamentals of Blazor with a simple project. After this we will move on to creating a more robust app.

Initial Setup

Download and install .NET Core 3.0 SDK from the following location:

https://dotnet.microsoft.com/download/dotnet-core/3.0

Download Visual Studio Community https://visualstudio.microsoft.com/vs/

Install with the following options:

image-20191104141230033

Your First Blazor App

Create a new Blazor app called BlazorWorkshop

1571330812725

Accept the default options

1571330833773

Once loaded, run the app (F5). You'll see this:

1571331705093

Click on the Counter option on the left and hit the ClickMe button to increase the value on the screen:

Note: if the counter doesn't work, you might be running in Internet Explorer or some other browser that is not supported. Try the latest version of Chrome.

Stop the app and open Counter.razor from the Pages folder

There are two sections here, the Razor markup and the code block as specified by @code {}

In the button tag, check out how the click handler is wired up, by using @onclick instead of onclick without an @ sign.

You can think of the IncrementCount() method as running in the browser, but it's actually running on the server. If this was a WebAssembly project (client-side Blazor), it would actually run in the browser.

Server-side Blazor ships UI changes down to the browser using a hidden SignalR hub. When the button is clicked, the browser sends a request via SignalR, the code executes on the server, and any UI changes are sent back to the browser.

Under the Hood

The following information comes right from the Blazor docs at the following URL:

https://docs.microsoft.com/en-us/aspnet/core/blazor/hosting-models?view=aspnetcore-3.0#blazor-server

With the Blazor Server hosting model, the app is executed on the server from within an ASP.NET Core app. UI updates, event handling, and JavaScript calls are handled over a SignalR connection.

The browser interacts with the app (md-images/blazor-server.png) on the server over a SignalR connection.

To create a Blazor app using the Blazor Server hosting model, use the ASP.NET Core Blazor Server App template (dotnet new blazorserver). The ASP.NET Core app hosts the Blazor Server app and creates the SignalR endpoint where clients connect.

The ASP.NET Core app references the app's Startup class to add:

The blazor.server.js script establishes the client connection. It's the app's responsibility to persist and restore app state as required (for example, in the event of a lost network connection).

The Blazor Server hosting model offers several benefits:

There are downsides to Blazor Server hosting:

The blazor.server.js script is served from an embedded resource in the ASP.NET Core shared framework.

Comparison to server-rendered UI

One way to understand Blazor Server apps is to understand how it differs from traditional models for rendering UI in ASP.NET Core apps using Razor views or Razor Pages. Both models use the Razor language to describe HTML content, but they significantly differ in how markup is rendered.

When a Razor Page or view is rendered, every line of Razor code emits HTML in text form. After rendering, the server disposes of the page or view instance, including any state that was produced. When another request for the page occurs, for instance when server validation fails and the validation summary is displayed:

A Blazor app is composed of reusable elements of UI called components. A component contains C# code, markup, and other components. When a component is rendered, Blazor produces a graph of the included components similar to an HTML or XML Document Object Model (DOM). This graph includes component state held in properties and fields. Blazor evaluates the component graph to produce a binary representation of the markup. The binary format can be:

A UI update in Blazor is triggered by:

The graph is rerendered, and a UI diff (difference) is calculated. This diff is the smallest set of DOM edits required to update the UI on the client. The diff is sent to the client in a binary format and applied by the browser.

A component is disposed after the user navigates away from it on the client. While a user is interacting with a component, the component's state (services, resources) must be held in the server's memory. Because the state of many components might be maintained by the server concurrently, memory exhaustion is a concern that must be addressed. For guidance on how to author a Blazor Server app to ensure the best use of server memory, see Secure ASP.NET Core Blazor Server apps.

(end of content from Blazor documentation)

Unhandled Exceptions

Let's watch what happens in the case of an unhandled exception:

Change the code to throw an exception:

When you run the app and click the button on the Counter page , Visual Studio breaks on the exception. Press F5 to continue. Now try to do anything at all in the app. It won't work. Worse, your user has no idea what happened, only that your app became brain-dead.

If you press F12 and look at the console output you'll see that Blazor has severed the SignalR connection completely.

image-20191029091552199

So the moral of the story is to execute server side code in try/catch blocks and fail gracefully. Of course, you should probably develop and test your code without error handling, to make development go faster. Once you're satisfied that your code works, then you can introduce the exception handling and figure out how to tell the user that something went wrong.

Other Oddities

One thing you will notice is that some of the standard features of Visual Studio don't work when you're editing code in a @code block in the razor page itself. For example, if you hit Ctrl-. on a variable name that you've changed, you expect the refactoring list to pop up.

To get the complete experience of editing code in Visual Studio, you probably want to put the code into a class, or a "code behind" class.

Code Behind

Let's do that. Right-click on the Pages folder, and add a new class called Counter.razor.cs

One thing you'll notice is that it automatically lands under the Counter.razor file in the Solution Explorer. Visual Studio is smart enough to know that's where it goes.

Replace the code in Counter.razor.cs with the following:

Changes to the code:

Let's go back to our Counter page and modify it.

Add the following line to the very top:

@inherits CounterCode

Then, simply delete the @code block. When you're done, it should look like this:

That's it! Now you can edit the code in Counter.razor.cs using the full C# editor and all of its features!

Build your first Component

Components are a great way to encapsulate UI in a single razor file (or file with code behind). Think of them like custom controls for the web written in Razor and C#.

We're going to start with something easy and end up with something really flexible and useful.

Right-click on the Pages folder and select Add, and then New Item.

From the list on the left, select Web. From the list on the right, select Razor Component. Name it TestComponent.razor.

1571345731987

It should look like this:

In your Index.razor page, add the following:

<TestComponent/>

It should look like this:

Press F5

1571352765877

OK, that was easy. Now let's add some parameters and display some data.

First, add a new class called Customer.cs:

Open the TestComponent.razor file and replace it with the following:

This component takes a list of customers as a parameter, then displays them in a list, using the CustomerId for the option value.

Now, replace the Index.razor file with this:

protected override void OnInitialized() occurs after the app loads and is ready for coding.

We're just adding 3 Customers to the list so we have something to work with. In the real world, you might call an API or a data service to get your initial data.

Press F5 and it should look like this:

1571354321401

Now that we have a component, let's wire up some events in it.

First, we will add a couple variables to the code block in TestComponent.razor:

SelectedCustomer will represent the customer that the user has clicked on.

DisplayMessage will be shown to the user as feedback.

Just below that, let's add a method that will get called whenever the user selects a new customer

If you've used LINQ at all, you should be familiar with the syntax to look up the selected customer.

Now we need to wire up the select tag to call CustomerSelected when the user clicks on a customer name with the @onchange directive:

Just below that, add a span to show the DisplayMessage:

Press F5 and you should see something like this when you select a customer:

image-20191029093556980

Now let's add an input tag and bind it to the selected customer's name. We will only show the input tag when a customer has been selected. Since the input tag let's us change the customer name, and we're updating the name on every key press, you will see an immediate change in the customer list on every key press.

Add this code below the span:

Press F5, select a customer, and start changing the name. You should see something like this:

image-20191029094039994

Let's recap what we just did:

Since the Customers collection is a parameter, the changes are reflected in the host page (Index.razor) immediately.

Now it's YOUR turn!

Make a change to the code so that the message to the user reflects the changes to the customer name as the user types, just like the select does.

image-20191029094454929

Scroll down for the solution, but try it yourself first!

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Solution:

The trick is to not copy the information into a string and display the string, but to display the SelectedCustomer.Name directly. For that you have to check for nulls, so move the span into the block where SelectedCustomer is not null:

Next Step: Add an event handler

Next we're going to add an event handler to our TestComponent, so that when a user is selected, we can notify our calling code.

In the code block, add this second parameter below the first one:

This defines an event that we can raise in the component, and the host (Index.razor) can handle. The EventCallback requires you pass something, but you can define that yourself. In our case we're going to pass back the SelectedCustomer.

In the CustomerSelected method, let's get rid of the code to set DisplayMessage (because we don't use it any more), and replace it with this:

Yes, it's an async call, and we can handle it in two ways. First is to simply add .Wait() to the end of our call, just like we're doing here. The second way is to make the entire CustomerSelected method async, and then call InvokeAsync with await:

Now let's go back to the host page (Index.razor) and handle the event.

First, let's add a method to handle the event, and a string to display to let the user know we have handled it:

Now, we can pass CustomerSelected as the event handler where we instantiate the component:

Finally, we can add a span to show DisplayMessage, and make the color green to differentiate it from the message displayed in the component:

Your entire Index.razor should look like this now:

Press F5, select a customer, and you should see something like this:

image-20191029100952121

Now it's YOUR turn!

Your assignment is to move all the code for the TestComponent into a code-behind class. While you're at it, remove the that shows the selected customer in red, and the DisplayMessage variable as well.

Scroll down for the solution, but try it first!

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Solution:

Your TestComponent.razor.cs file should look like this:

and your TestComponent.razor file should look like this:

Next Step: Add API Controller

It's really easy to add an API controller to our Blazor app.

First, create a Controllers folder in the project. Then, right-click, select Add, then Controller

Select the API Controller with read/write actions and click the Add button.

image-20191029101922872

Name it CustomerController and click the Add button.

image-20191029101937137

The next step is to move the code that creates customers out of Index.razor and into this controller.

Change the first method to this:

Next, right-click on the Data folder and add a new class called CustomerService (irony).

Change the baseURL accordingly. You can get that by simply running the app and copying the url out of the browser window, or by reading the sslPort from iisSettings in launchSettings.json under the Properties folder.

We will call this service from the same place in Index.razor where we created the customers. We also need to call this method asynchronously, so we'll change OnInitialized to OnInitializedAsync.

Of course, you now need this at the top right under the @page directive:

There's one more change we need to do, and that's in startup.cs. Add the following line to the UseEndpoints list at the bottom of the code:

So your UseEndpoints call should look like this:

Now press F5. Your screen shouldn't look any different than it did before, except that now you're retrieving your data from an API endpoint.

image-20191029111821449

Now it's YOUR turn!

Add three more customers to the list of Customers returned by the API with the following names and CustomerId values:

Next Step - Add a Get API for a specific customer

Next, we will add a Get API to retrieve a customer by Id, and some logic in the component to reset a customer after its name has been edited.

In the CustomerController.cs file, move the code that creates customers to a private method and change the Get to call that method:

Now we can modify the second Get method (that passes an Id) to return the requested customer:

You can test this immediately. Press F5 and append /api/customer/2 to the url:

image-20191029114457244

Next, add a method to the CustomerService class to retrieve the customer from the API:

Now, we need to change up the Index.razor file a bit to be a little more intelligent:

We added a SelectedCustomer variable and removed the DisplayMessage stuff, just like we did earlier in the component itself.

We also pass SelectedCustomer as a parameter, so that the values are bound.

Next, let's modify the markup in the component to make SelectedCustomer a parameter, and also include a Reset button. First, let's add the Reset button to the markup:

Next, let's modify the code behind:

First, we make SelectedCustomer a parameter:

Next, we add an EventCallback for a CustomerResetEvent:

Finally, add the ResetButtonClicked method called when the user clicks the Reset button.

Note that we are passing the CustomerId. There's no need to pass the entire Customer object.

Now, let's jump back into the Index.razor host page and wire up the call to the API via CustomerService.

Add the following event handler:

Here we call, GetCustomer which calls the API and returns the customer. After a quick check to make sure we actually retrieved a customer, we replace the customer in the Customers list with the updated (original) version and replace our SelectedCustomer as well.

Finally, we have to pass the CustomerResetEvent event handler as a parameter:

Press F5, select the first customer and change the name:

image-20191029122237843

Now hit the Reset button and you'll see the name revert to its original value:

image-20191029122247632

Note: if you notice that the Reset button stays depressed after you click it the first time, you might be using a non-chromium verison of Edge. Some students have noticed this behavior, which does not happen if you use Chrome or an updated version of Edge.

Next Step - Add a Customer via the API

Right now, our list of customers only exists in memory as long as the app is running, so let's do a little cheating and persist the Customers collection in the API to a JSON file.

First, we need to move the Customers list out to the class level and add a string variable for our file name. Add these variables to CustomerController.cs:

Next, let's add private methods to load and save the Customers collection:

You'll also need this:

Next, let's modify the GetAllCustomers method to populate the class-level Customers collection:

Now, we need to ensure the data is loaded when the controller is instantiated, so we add a call to LoadData() in the constructor:

Now, no matter which API method we call, Customers will always be populated with the latest data.

Change the two Get methods as follows:

Finally, we will flesh out the Post method, which gets called to add a customer:

Now, let's flip over to CustomerService.cs and add a method to call this API:

And we'll need this, then:

Now we're ready to update our UI. Edit the component markup (TestComponent.razor) and add this markup right below the select tag:

Now we have to add a couple items to the code behind:

NewCustomerName is simply a string bound to the new input tag value.

CustomerAdding() gets called when the user clicks the Add button.

Finally, we added an AddCustomerEvent EventCallback that we can handle in the host.

Let's do that now. Open Index.razor and add this method to the Code block:

The first line gets the customer with the highest CustomerId value. We will then increment that for our new Customer. After calling the API to add the new Customer, we need to refresh the Customers list. We could, I suppose, just add this new Customer to the Customers collection, but we might as well get the source of truth, in case anyone else has added or modified customers.

Lastly, we need to add our CustomerAdding event to the parameters when we instantiate the component:

Press F5. Enter a new name in the input tag:

image-20191029135915085

Press the Add button, then scroll the list to the bottom, and you should see the new customer listed:

image-20191029135949678

Select it. The editor and reset button appears.

image-20191029140006820

You can test the persistence by re-running the app. If you want to start over, delete the customers.json file.

Now it's YOUR turn!

Add code to clear the Add Customer input tag after clicking the Add button.

Scroll down for the solution, but try it yourself first!

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Solution:

The best place to do this is in the component itself after the host has added the new customer:

Next Step: Update a Customer

Next we'll flesh out the code to update a customer starting with the API:

Next, add this method to CustomerService.cs:

Next, we'll add the markup to the component to support it. We can also take this opportunity to pretty up our component edit fields by putting a div around them with some padding and a new background color for contrast:

Next, add the required code to the code behind:

Next, add an event handler in Index.razor:

And finally, pass the event handler parameter to the component:

Press F5, select the first Customer, and change the name:

image-20191029172120954

Now press the Update button, and then press the Reset button. You'll notice that the name has been permanently changed.

Now it's YOUR turn!

Add the ability to delete the selected customer. Start with the API, then add the CustomerService method, then add the delete button and code to the component, which should raise an event to the host to notify that the user wants to delete a customer. You do not need to pass the whole customer object, just the CustomerId.

Scroll down for the solution.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Solution:

Delete Action in API:

CustomerSercvice method:

Add update button to component:

Code behind:

Host component instantiation passing delete event handler:

Event handler:

Take your new app for a spin! Press F5

Next Step: Form Validation

Blazor has built-in versions of DOM input objects that support validation. What's more, you can define the rules as property attributes in your classes.

Let's add to our Customer class and set up some rules:

The Name and Email properties are both required and both have a maximum length of 50.

The Email property has been tagged as an Email address. Blazor will validate this for you. No RegEx required!

Now let's replace the input tags in the component with the new Blazor controls, and wrap them in an EditForm element. For the time being, let's comment out the Add Customer UI:

Here we tell the EditForm we are using the SelectedCustomer object as the validation model. It will pull the attributes from the Customer class and enforce them.

Instead of using Input elements, we are now using InputText elements, which have the features we need to interact with the validators.

Notice that all the buttons except for the Submit button are defined with type=button so they don't trigger the submit.

Next, we need to modify the code that creates our initial customers in the CustomerController.cs file to also include an email address:

Before you run, locate and delete the customers.json file in the Solution Explorer.

Press F5, select a customer and try to fire the validation by making the name too long or the email invalid, or try removing the values all together.

image-20191104230736650

Next Step: Add Customer with Validation

Next, let's use the component to add a new customer so we can take advantage of the validation features of the EditForm element.

Change the commented-out Add Button in TestComponent.razor to this:

Next, in the code behind, we'll modify the AddCustomerEvent EventCallback to pass a Customer rather than just a name, and also add some support code:

When the Add button is clicked, we set the SelectedCustomer to a new customer, and the binding takes over, complete with validation.

The Adding boolean tells us what event to raise when the Update button is clicked:

Now, we need to modify the CustomerAdding method in Index.razor to accept a Customer. It actually simplifies things a little:

Code Check

At this point your code should look like this:

Index.razor:

TestComponent.razor:

TestComponent.razor.cs:

CustomerController.cs:

CustomerService.cs:

Next Step: Final Touches

The next thing we will do is fortify our code with some error handling (remember, unhandled exceptions make for unhappy customers) and a nice way to notify the user of what is happening. We'll also refactor a bit of the code to make sure the UI stays in sync with the SelectedCustomer. Most of these changes are in Index.razor.

First, let's replace this:

with this:

which means we need a couple new variables:

Let's add a little error handling to the code that gets our initial data set:

Now, let's remove the try/catch blocks from the code in CustomerService.cs:

You can test it by munging the URL to the API in the GetAllCustomers() method.

Next, let's add some error handling to the Update code:

This time we not only display an error message in red if something goes wrong, but we display a message in green if everything goes right.

Changes to the code for Delete, Reset, and Add are similar:

That's all good. Now we can rest assured that no API based problems will take us down.

Now, let's fix some behavioral problems. What happens when you Add a new customer? It goes to the bottom of the list. It would be good to select it once added. In order to do that, we have to modify the component starting with the select element.

Modify the select element in TestComponent.razor as follows:

This ensures that any time we're displaying customers, the SelectedCustomer is always selected in the list.

Let's modify the code that handles deletes (in Index.razor) to select the first customer after deleting.

Now when you delete, the SelectedCustomer isn't still set to the one we just deleted!

Issue: InputText doesn't support oninput

This is a problem with an easy workaround. The problem is that since we changed over to use the validation-aware InputText control, we don't have the ability to update on every keystroke. Fortunately there is a workaround that only takes 2 lines of code.

Right-click on the Pages folder and add a new Razor Component named InstantInput.razor.

Replace the contents with these two lines:

Now change your InputText elements to InstantInput elements and boom! Updates are instant on each keystroke.

That's it for Part 1. We have covered the fundamentals of components and how Blazor binding works, and we've learned a few cool tricks in the process.

Let's now move on to Part 2, where we'll create a more sophisticated app with EF Core and more complex components.